---
title: 'How to use Prisma ORM with Clerk Auth and Astro'
metaTitle: 'How to use Prisma ORM and Prisma Postgres with Clerk Auth and Astro'
description: 'Learn how to use Prisma ORM in an Astro app with Clerk Auth'
sidebar_label: 'Clerk (with Astro)'
image: '/img/guides/prisma-clerk-astro-cover.png'
completion_time: '25 min'
community_section: true
---

## Introduction

[Clerk](https://clerk.com/) is a drop-in auth provider that handles sign-up, sign-in, user management, and webhooks so you don't have to.

In this guide you'll wire Clerk into a brand-new [Astro](https://astro.build/) app and persist users in a [Prisma Postgres](https://prisma.io/postgres) database. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/clerk-astro).

## Prerequisites

- [Node.js 20+](https://nodejs.org)
- [Clerk account](https://clerk.com)
- [ngrok account](https://ngrok.com)

## 1. Set up your project

Create a new Astro project:

```terminal
npm create astro@latest
```

It will prompt you to customize your setup. Choose the defaults:

:::info

- *How would you like to start your new project?* `Empty`
- *Install dependencies?* `Yes`
- *Initialize a new git repository?* `Yes`

:::

Navigate into the newly created project directory:

```terminal
cd <your-project-name>
```

## 2. Set up Clerk

### 2.1. Create a new Clerk application

[Sign in](https://dashboard.clerk.com/sign-in) to Clerk and navigate to the home page. From there, press the `Create Application` button to create a new application. Enter a title, select your sign-in options, and click `Create Application`.

:::info

For this guide, the Google, Github, and Email sign in options will be used.

:::

Install the Clerk Astro SDK and Node adapter:

```terminal
npm install @clerk/astro @astrojs/node
```

In the Clerk Dashboard, navigate to the **API keys** page. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys. Paste your keys into `.env` in the root of your project:

```dotenv file=.env
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>
```

### 2.2. Configure Astro with Clerk

Astro needs to be configured for server-side rendering (SSR) with the Node adapter to work with Clerk. Update your `astro.config.mjs` file to include the Clerk integration and enable SSR:

```javascript file=astro.config.mjs
import { defineConfig } from 'astro/config'
//add-start
import node from '@astrojs/node'
import clerk from '@clerk/astro'
//add-end

export default defineConfig({
  //add-start
  integrations: [clerk()],
  adapter: node({ mode: 'standalone' }),
  output: 'server',
  //add-end
})
```

### 2.3. Set up Clerk middleware

The `clerkMiddleware` helper enables authentication across your entire application. Create a `middleware.ts` file in the `src` directory:

```typescript file=src/middleware.ts
import { clerkMiddleware } from '@clerk/astro/server'

export const onRequest = clerkMiddleware()
```

### 2.4. Add Clerk UI to your page

Update your `src/pages/index.astro` file to import the Clerk authentication components:

```html file=src/pages/index.astro
---
//add-start
import {
    SignedIn,
    SignedOut,
    UserButton,
    SignInButton,
} from "@clerk/astro/components";
//add-end
---

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
        <meta name="viewport" content="width=device-width" />
        <meta name="generator" content={Astro.generator} />
        <title>Astro</title>
    </head>
    <body>
    </body>
</html>
```

Now add a header with conditional rendering to show sign-in buttons for unauthenticated users and a user button for authenticated users:

```html file=src/pages/index.astro
---
import {
    SignedIn,
    SignedOut,
    UserButton,
    SignInButton,
} from "@clerk/astro/components";
---

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
        <meta name="viewport" content="width=device-width" />
        <meta name="generator" content={Astro.generator} />
        <title>Astro</title>
    </head>
    <body>
        //add-start
        <header>
            <SignedOut>
                <SignInButton mode="modal" />
            </SignedOut>
            <SignedIn>
                <UserButton />
            </SignedIn>
        </header>
        //add-end
    </body>
</html>
```

## 3. Install and configure Prisma

### 3.1. Install dependencies

To get started with Prisma, you'll need to install a few dependencies:

```terminal
npm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pg
```

:::info

If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/overview/databases/database-drivers).

:::

Once installed, initialize Prisma in your project:

```terminal
npx prisma init --db
```

:::info
You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for the database like "My Clerk Astro Project"
:::

This will create:

- A `prisma/` directory with a `schema.prisma` file
- A `prisma.config.ts` file with your Prisma configuration
- A `.env` file with a `DATABASE_URL` already set

### 3.2. Define your Prisma Schema

Add a `User` model that will store authenticated user information from Clerk. The `clerkId` field uniquely links each database user to their Clerk account:

```prisma file=prisma/schema.prisma
generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

//add-start
model User {
  id      Int     @id @default(autoincrement())
  clerkId String  @unique
  email   String  @unique
  name    String?
}
//add-end
```

Run the following command to create the database tables:

```terminal
npx prisma migrate dev --name init
```

After the migration is complete, generate the Prisma Client:

```terminal
npx prisma generate
```

This generates the Prisma Client in the `src/generated/prisma` directory.

### 3.3. Create TypeScript environment definitions

Create an `env.d.ts` file in your `src` directory to provide TypeScript definitions for your environment variables:

```terminal
touch src/env.d.ts
```

Add type definitions for all the environment variables your application uses:

```typescript file=src/env.d.ts
interface ImportMetaEnv {
  readonly DATABASE_URL: string;
  readonly CLERK_WEBHOOK_SIGNING_SECRET: string;
  readonly CLERK_SECRET_KEY: string;
  readonly PUBLIC_CLERK_PUBLISHABLE_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
```

### 3.4. Create a reusable Prisma Client

In the `src` directory, create a `lib` directory and a `prisma.ts` file inside it:

```terminal
mkdir src/lib
touch src/lib/prisma.ts
```

Initialize the Prisma Client with the PostgreSQL adapter:

```typescript file=src/lib/prisma.ts
import { PrismaClient } from "../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
  connectionString: import.meta.env.DATABASE_URL,
});

const prisma = new PrismaClient({
  adapter,
});

export default prisma;
```

## 4. Wire Clerk to the database

### 4.1. Create a Clerk webhook endpoint

Webhooks allow Clerk to notify your application when events occur, such as when a user signs up. You'll create an API route to handle these webhooks and sync user data to your database.

Create the directory structure and file for the webhook endpoint:

```terminal
mkdir -p src/pages/api/webhooks
touch src/pages/api/webhooks/clerk.ts
```

Import the necessary dependencies:

```typescript file=src/pages/api/webhooks/clerk.ts
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";
```

Create the `POST` handler that Clerk will call. The `verifyWebhook` function validates that the request actually comes from Clerk using the signing secret:

```typescript file=src/pages/api/webhooks/clerk.ts
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

//add-start
export const POST: APIRoute = async ({ request }) => {
  try {
    const evt = await verifyWebhook(request, {
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
    });
    const { id } = evt.data;
    const eventType = evt.type;
    console.log(
      `Received webhook with ID ${id} and event type of ${eventType}`,
    );
  } catch (err) {
    console.error("Error verifying webhook:", err);
    return new Response("Error verifying webhook", { status: 400 });
  }
};
//add-end
```

When a new user is created, they need to be stored in the database.

You'll do that by checking if the event type is `user.created` and then using Prisma's `upsert` method to create a new user if they don't exist:

```typescript file=src/pages/api/webhooks/clerk.ts
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {
  try {
    const evt = await verifyWebhook(request, {
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
    });
    const { id } = evt.data;
    const eventType = evt.type;
    console.log(
      `Received webhook with ID ${id} and event type of ${eventType}`,
    );

    //add-start
    if (eventType === "user.created") {
      const { id, email_addresses, first_name, last_name } = evt.data;
      await prisma.user.upsert({
        where: { clerkId: id },
        update: {},
        create: {
          clerkId: id,
          email: email_addresses[0].email_address,
          name: `${first_name} ${last_name}`,
        },
      });
    }
    //add-end
  } catch (err) {
    console.error("Error verifying webhook:", err);
    return new Response("Error verifying webhook", { status: 400 });
  }
};
```

Finally, return a response to Clerk to confirm the webhook was received:

```typescript file=src/pages/api/webhooks/clerk.ts
import { verifyWebhook } from "@clerk/astro/webhooks";
import type { APIRoute } from "astro";
import prisma from "../../../lib/prisma";

export const POST: APIRoute = async ({ request }) => {
  try {
    const evt = await verifyWebhook(request, {
      signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
    });
    const { id } = evt.data;
    const eventType = evt.type;
    console.log(
      `Received webhook with ID ${id} and event type of ${eventType}`,
    );

    if (eventType === "user.created") {
      const { id, email_addresses, first_name, last_name } = evt.data;
      await prisma.user.upsert({
        where: { clerkId: id },
        update: {},
        create: {
          clerkId: id,
          email: email_addresses[0].email_address,
          name: `${first_name} ${last_name}`,
        },
      });
    }

    //add-next-line
    return new Response("Webhook received", { status: 200 });
  } catch (err) {
    console.error("Error verifying webhook:", err);
    return new Response("Error verifying webhook", { status: 400 });
  }
};
```

### 4.2. Expose your local app for webhooks

You'll need to expose your local app for webhooks with [ngrok](https://ngrok.com/). This will allow Clerk to reach your `/api/webhooks/clerk` route to push events like `user.created`.

Start your development server:

```terminal
npm run dev
```

In a separate terminal window, install ngrok globally and expose your local app:

```terminal
npm install --global ngrok
ngrok http 4321
```

Copy the ngrok `Forwarding URL` (e.g., `https://a65a60261342.ngrok-free.app`). This will be used to configure the webhook URL in Clerk.

### 4.3. Configure Astro to allow ngrok connections

Astro needs to be configured to accept connections from the ngrok domain. Update your `astro.config.mjs` to include the ngrok host in the allowed hosts list:

```javascript file=astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import clerk from "@clerk/astro";

export default defineConfig({
  integrations: [clerk()],
  adapter: node({ mode: "standalone" }),
  output: "server",
  //add-start
  server: {
    allowedHosts: ["localhost", "<your-ngrok-subdomain>.ngrok-free.app"],
  },
  //add-end
});
```

:::note

Replace `<your-ngrok-subdomain>` with the subdomain from your ngrok URL. For example, if your ngrok URL is `https://a65a60261342.ngrok-free.app`, use `a65a60261342.ngrok-free.app`.

:::

### 4.4. Register the webhook in Clerk

Navigate to the ***Webhooks*** section of your Clerk application located near the bottom of the ***Configure*** tab under ***Developers***.

Click ***Add Endpoint*** and paste the ngrok URL into the ***Endpoint URL*** field and add `/api/webhooks/clerk` to the end. It should look similar to this:

```text
https://a65a60261342.ngrok-free.app/api/webhooks/clerk
```

Subscribe to the **user.created** event by checking the box next to it under ***Message Filtering***.

Click ***Create*** to save the webhook endpoint.

Copy the ***Signing Secret*** and add it to your `.env` file:

```dotenv file=.env
# Prisma
DATABASE_URL=<your-database-url>

# Clerk
PUBLIC_CLERK_PUBLISHABLE_KEY=<your-publishable-key>
CLERK_SECRET_KEY=<your-secret-key>
//add-next-line
CLERK_WEBHOOK_SIGNING_SECRET=<your-signing-secret>
```

Restart your dev server to pick up the new environment variable:

```terminal
npm run dev
```

### 4.5. Test the integration

Navigate to `http://localhost:4321` in your browser and sign in using any of the sign-up options you configured in Clerk.

Open Prisma Studio to verify that the user was created in your database:

```terminal
npx prisma studio
```

You should see a new user record with the Clerk ID, email, and name from your sign-up.

:::note

If you don't see a user record, there are a few things to check:
- Delete your user from the Users tab in Clerk and try signing up again.
- Check your ngrok URL and ensure it's correct *(it will change every time you restart ngrok)*.
- Verify your Clerk webhook is pointing to the correct ngrok URL.
- Make sure you've added `/api/webhooks/clerk` to the end of the webhook URL.
- Ensure you've subscribed to the **user.created** event in Clerk.
- Confirm you've added the ngrok host to `allowedHosts` in `astro.config.mjs` and removed `https://`.
- Check the terminal running `npm run dev` for any error messages.

:::

You've successfully built an Astro application with Clerk authentication and Prisma, creating a foundation for a secure and scalable full-stack application that handles user management and data persistence with ease.

## Next steps

Now that you have a working Astro app with Clerk authentication and Prisma connected to a Prisma Postgres database, you can:

- Add user profile management and update functionality
- Build protected API routes that require authentication
- Extend your schema with additional models related to users
- Deploy to your preferred hosting platform and set your production webhook URL in Clerk
- Enable query caching with [Prisma Postgres](/postgres/database/caching) for better performance

### More info

- [Prisma Documentation](/orm/overview/introduction)
- [Astro Documentation](https://docs.astro.build)
- [Clerk Documentation](https://clerk.com/docs)
